#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
瀚越包装方案匹配工具 - 测试脚本
按照项目大纲中的数据处理流程逐步实现，并生成可视化文件
"""

import os
import sys
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
import json
import time
from datetime import datetime
import logging

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 添加项目路径
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))

# 导入项目模块
from src.excel_handler.excel_processor import ExcelProcessor
from src.data_processor.data_handler import DataHandler
from src.configs.config_manager import ConfigManager
from src.utils.logger import setup_logger

# 设置日志
logger = setup_logger("test_script", level=logging.INFO)

class ProcessTester:
    """处理流程测试类"""
    
    def __init__(self):
        """初始化测试器"""
        # 设置日志
        self.logger = setup_logger("test_script", level=logging.INFO)
        
        # 创建必要的目录
        self.test_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test_results'))
        self.visual_dir = os.path.join(self.test_dir, 'visualizations')
        self.data_dir = os.path.join(self.test_dir, 'processed_data')
        
        for directory in [self.test_dir, self.visual_dir, self.data_dir]:
            Path(directory).mkdir(parents=True, exist_ok=True)
        
        # 初始化组件
        self.excel_processor = ExcelProcessor()
        self.config_manager = ConfigManager()
        self.config = self.config_manager.load_config()
        self.data_handler = DataHandler(self.config)
        
        # 数据存储
        self.task_data = None
        self.history_data = None
        self.processed_data = {}
        
        # 原始文件路径
        self.task_file_path = None
        self.history_file_path = None
        
    def run_test(self, task_file_path=None, history_file_path=None):
        """
        运行完整测试流程
        
        Args:
            task_file_path: 任务数据文件路径
            history_file_path: 历史数据文件路径
        """
        # 使用用户指定的数据源文件作为默认值
        if task_file_path is None:
            task_file_path = "d:\\Trae\\HYBZ\\瀚的发货用包装箱任务.xlsx"
        if history_file_path is None:
            history_file_path = "d:\\Trae\\HYBZ\\瀚越已发货包装数据总表.xlsx"
            
        logger.info("开始测试流程")
        
        # 1. 前处理阶段测试
        logger.info("=== 开始前处理阶段测试 ===")
        # 保存带有阶段前缀的预处理数据
        self._save_dataframe(self.task_data, "原始任务数据", "1-1-")
        self._save_dataframe(self.history_data, "原始历史数据", "1-2-")
        self._test_pre_processing(task_file_path, history_file_path)
        
        # 2. 数据预处理阶段测试
        logger.info("=== 开始数据预处理阶段测试 ===")
        # 保存带有阶段前缀的预处理阶段数据
        self._save_dataframe(self.task_data, "格式化任务数据", "2-1-")
        self._save_dataframe(self.history_data, "格式化历史数据", "2-2-")
        self._test_data_preprocessing()
        
        # 3. 任务分类与数据匹配阶段测试
        logger.info("=== 开始任务分类与数据匹配阶段测试 ===")
        # 保存带有阶段前缀的分类匹配数据
        self._test_task_classification_and_matching()
        self._save_dataframe(self.task_data, "任务分类结果", "3-1-")
        self._save_dataframe(self.task_data, "数据匹配结果", "3-2-")
        
        # 4. 数据后处理阶段测试
        logger.info("=== 开始数据后处理阶段测试 ===")
        self._test_post_processing()
        
        logger.info("测试流程完成")
        return True
    
    def _test_pre_processing(self, task_file_path, history_file_path):
        """测试前处理阶段"""
        try:
            # 文件选取与数据导入
            logger.info("执行文件选取与数据导入")
            
            # 保存原始文件路径
            self.task_file_path = task_file_path
            self.history_file_path = history_file_path
            
            # 如果未提供文件路径，使用示例数据
            if task_file_path is None or history_file_path is None:
                logger.warning("未提供文件路径，将使用模拟数据进行测试")
                self._generate_mock_data()
            else:
                self.task_data = self.excel_processor.read_excel(task_file_path)
                self.history_data = self.excel_processor.read_excel(history_file_path)
                
                if self.task_data is None or self.history_data is None:
                    raise Exception("无法读取数据文件，使用模拟数据继续测试")
            
            # 保存原始数据
            self._save_dataframe(self.task_data, "原始任务数据", "1-1-")
            self._save_dataframe(self.history_data, "原始历史数据", "1-2-")
            
            # 数据格式化
            logger.info("执行数据格式化")
            self.task_data = self._format_task_data(self.task_data)
            self.history_data = self._format_history_data(self.history_data)
            
            # 保存格式化数据
            self.processed_data['formatted_task_data'] = self.task_data
            self.processed_data['formatted_history_data'] = self.history_data
            
            self._save_dataframe(self.task_data, "格式化任务数据", "1-3-")
            self._save_dataframe(self.history_data, "格式化历史数据", "1-4-")
            
            # 数据统计
            logger.info("执行数据统计")
            stats = self._calculate_statistics()
            
            # 生成统计可视化
            self._visualize_statistics(stats)
            
            logger.info("前处理阶段测试完成")
            
        except Exception as e:
            logger.error(f"前处理阶段测试失败: {str(e)}")
            raise
    
    def _test_data_preprocessing(self):
        """测试数据预处理阶段"""
        try:
            # 历史包装类型判定
            logger.info("执行历史包装类型判定")
            # 传递原始Excel文件路径以实现精确的合并单元格识别
            self.history_data = self._determine_packaging_type(self.history_data, self.history_file_path)
            
            # 保存包装类型判定结果
            self.processed_data['history_with_packaging_type'] = self.history_data
            self._save_dataframe(self.history_data, "历史数据包装类型判定", "2-1-")
            
            # 可视化包装类型分布
            self._visualize_packaging_types()
            
            # 数据合并
            logger.info("执行数据合并")
            self.task_data = self._merge_task_data(self.task_data)
            
            # 保存合并后的数据
            self.processed_data['merged_task_data'] = self.task_data
            self._save_dataframe(self.task_data, "合并后任务数据", "2-2-")
            
            # 建立产品包装方案库
            logger.info("建立产品包装方案库")
            product_package_lib = self._build_product_package_library()
            
            # 转换numpy类型为Python原生类型
            def convert_numpy_types(obj):
                if isinstance(obj, np.integer):
                    return int(obj)
                elif isinstance(obj, np.floating):
                    return float(obj)
                elif isinstance(obj, np.ndarray):
                    return obj.tolist()
                elif isinstance(obj, np.bool_) or isinstance(obj, bool):
                    return bool(obj)
                return obj
            
            # 深度转换字典中的numpy类型
            def deep_convert(obj):
                if isinstance(obj, dict):
                    return {key: deep_convert(value) for key, value in obj.items()}
                elif isinstance(obj, list):
                    return [deep_convert(item) for item in obj]
                else:
                    return convert_numpy_types(obj)
            
            # 转换包装方案库
            product_package_lib = deep_convert(product_package_lib)
            
            # 保存产品包装方案库
            with open(os.path.join(self.data_dir, "2-3-产品包装方案库.json"), 'w', encoding='utf-8') as f:
                json.dump(product_package_lib, f, ensure_ascii=False, indent=2)
            
            # 产品归类
            logger.info("执行产品归类")
            self.task_data = self._classify_products(product_package_lib)
            
            # 保存产品归类结果
            self.processed_data['classified_task_data'] = self.task_data
            self._save_dataframe(self.task_data, "产品归类后任务数据", "2-4-")
            
            # 可视化产品归类结果
            self._visualize_product_classification()
            
            logger.info("数据预处理阶段测试完成")
            
        except Exception as e:
            logger.error(f"数据预处理阶段测试失败: {str(e)}")
            raise
    
    def _test_task_classification_and_matching(self):
        """测试任务分类与数据匹配阶段"""
        try:
            # 加载产品包装方案库
            with open(os.path.join(self.data_dir, "2-3-产品包装方案库.json"), 'r', encoding='utf-8') as f:
                product_package_lib = json.load(f)
            
            # 任务分类处理
            logger.info("执行任务分类处理")
            self.task_data = self._classify_tasks(product_package_lib)
            
            # 保存任务分类结果
            self.processed_data['classified_tasks'] = self.task_data
            self._save_dataframe(self.task_data, "任务分类结果", "3-1-")
            
            # 可视化任务分类结果
            self._visualize_task_classification()
            
            # 数据匹配
            logger.info("执行数据匹配")
            self.task_data = self._match_data()
            
            # 保存数据匹配结果
            self.processed_data['matched_data'] = self.task_data
            self._save_dataframe(self.task_data, "数据匹配结果", "3-2-")
            
            # 可视化匹配结果
            self._visualize_matching_results()
            
            # 数据校验与修正
            logger.info("执行数据校验与修正")
            self.task_data = self._validate_and_correct_data()
            
            # 保存校验后的数据
            self.processed_data['validated_data'] = self.task_data
            self._save_dataframe(self.task_data, "数据校验与修正结果", "3-3-")
            
            logger.info("任务分类与数据匹配阶段测试完成")
            
        except Exception as e:
            logger.error(f"任务分类与数据匹配阶段测试失败: {str(e)}")
            raise
    
    def _test_post_processing(self):
        """测试数据后处理阶段"""
        try:
            # 数据格式化输出
            logger.info("执行数据格式化输出")
            final_data = self._format_output_data()
            
            # 保存最终结果
            output_file = os.path.join(self.data_dir, "4-1-最终输出结果.xlsx")
            success = self.excel_processor.write_excel(final_data, output_file)
            
            if success:
                logger.info(f"最终结果已保存至: {output_file}")
                
                # 生成最终结果的可视化报告
                self._generate_final_report()
            else:
                raise Exception("保存最终结果失败")
            
            logger.info("数据后处理阶段测试完成")
            
        except Exception as e:
            logger.error(f"数据后处理阶段测试失败: {str(e)}")
            raise
    
    def _generate_mock_data(self):
        """生成模拟测试数据"""
        logger.info("生成模拟测试数据")
        
        # 生成模拟任务数据
        np.random.seed(42)
        
        # 客户简称列表
        customers = ['客户A', '客户B', '客户C', '客户D', '客户E']
        
        # 规格型号列表
        models = ['型号1', '型号2', '型号3', '型号4', '型号5', '型号6', '型号7', '型号8']
        
        # 包装箱方案列表
        packages = ['方案A', '方案B', '方案C', '方案D', '方案E']
        
        # 生成任务数据
        task_rows = []
        for i in range(100):
            row = {
                '客户订单号': f'ORD{i+1000}',
                '行号': i + 1,
                '客户简称': np.random.choice(customers),
                '规格型号': np.random.choice(models),
                '数量': np.random.randint(1, 100),
                '预完工日期': f'2024-{np.random.randint(1, 13):02d}-{np.random.randint(1, 29):02d}',
                '包装箱外口尺寸': '',
                '重量': '',
                '包装箱方案': ''
            }
            task_rows.append(row)
        
        self.task_data = pd.DataFrame(task_rows)
        
        # 生成历史数据
        history_rows = []
        for i in range(300):
            quantity = np.random.choice([1, 5, 10, 20, 50, 100])
            row = {
                '客户简称': np.random.choice(customers),
                '规格型号': np.random.choice(models),
                '数量': quantity,
                '预完工日期': f'2024-{np.random.randint(1, 13):02d}-{np.random.randint(1, 29):02d}',
                '包装箱外口尺寸': f'{np.random.randint(30, 200)}x{np.random.randint(30, 200)}x{np.random.randint(30, 200)}',
                '重量': round(np.random.uniform(5.0, 50.0), 2),
                '包装箱方案': np.random.choice(packages)
            }
            history_rows.append(row)
        
        self.history_data = pd.DataFrame(history_rows)
        
        logger.info(f"生成模拟任务数据 {len(self.task_data)} 行，历史数据 {len(self.history_data)} 行")
    
    def _format_task_data(self, df):
        """格式化任务数据"""
        # 创建副本以避免修改原始数据
        formatted_df = df.copy()
        
        # 确保必要的列存在
        required_columns = ['客户订单号', '行号', '客户简称', '规格型号', '数量', '预完工日期']
        for col in required_columns:
            if col not in formatted_df.columns:
                formatted_df[col] = ''
        
        # 数据类型转换
        if '数量' in formatted_df.columns:
            formatted_df['数量'] = pd.to_numeric(formatted_df['数量'], errors='coerce').fillna(0).astype(int)
        
        # 日期格式化
        if '预完工日期' in formatted_df.columns:
            # 处理不同格式的日期
            formatted_df['预完工日期'] = pd.to_datetime(formatted_df['预完工日期'], errors='coerce')
            formatted_df['预完工日期'] = formatted_df['预完工日期'].dt.strftime('%Y-%m-%d')
        
        # 填充缺失值
        formatted_df = formatted_df.fillna('')
        
        return formatted_df
    
    def _format_history_data(self, df):
        """格式化历史数据"""
        # 创建副本以避免修改原始数据
        formatted_df = df.copy()
        
        # 确保必要的列存在
        required_columns = ['客户简称', '规格型号', '数量', '预完工日期', '包装箱外口尺寸', '重量', '包装箱方案']
        for col in required_columns:
            if col not in formatted_df.columns:
                formatted_df[col] = ''
        
        # 数据类型转换
        if '数量' in formatted_df.columns:
            formatted_df['数量'] = pd.to_numeric(formatted_df['数量'], errors='coerce').fillna(0).astype(int)
        
        if '重量' in formatted_df.columns:
            formatted_df['重量'] = pd.to_numeric(formatted_df['重量'], errors='coerce').fillna(0.0)
        
        # 日期格式化
        if '预完工日期' in formatted_df.columns:
            formatted_df['预完工日期'] = pd.to_datetime(formatted_df['预完工日期'], errors='coerce')
            formatted_df['预完工日期'] = formatted_df['预完工日期'].dt.strftime('%Y-%m-%d')
        
        # 填充缺失值
        formatted_df = formatted_df.fillna('')
        
        return formatted_df
    
    def _calculate_statistics(self):
        """计算数据统计信息"""
        stats = {}
        
        # 任务数据统计
        stats['task_data'] = {
            'total_rows': len(self.task_data),
            'unique_customers': self.task_data['客户简称'].nunique(),
            'unique_models': self.task_data['规格型号'].nunique(),
            'total_quantity': self.task_data['数量'].sum(),
            'date_range': {
                'min': self.task_data['预完工日期'].min(),
                'max': self.task_data['预完工日期'].max()
            }
        }
        
        # 历史数据统计
        stats['history_data'] = {
            'total_rows': len(self.history_data),
            'unique_customers': self.history_data['客户简称'].nunique(),
            'unique_models': self.history_data['规格型号'].nunique(),
            'total_quantity': self.history_data['数量'].sum(),
            'avg_weight': self.history_data['重量'].mean() if '重量' in self.history_data.columns else 0
        }
        
        # 转换numpy类型为Python原生类型
        def convert_numpy_types(obj):
            if isinstance(obj, np.integer):
                return int(obj)
            elif isinstance(obj, np.floating):
                return float(obj)
            elif isinstance(obj, np.ndarray):
                return obj.tolist()
            return obj
        
        # 深度转换统计数据中的numpy类型
        def deep_convert(obj):
            if isinstance(obj, dict):
                return {key: deep_convert(value) for key, value in obj.items()}
            elif isinstance(obj, list):
                return [deep_convert(item) for item in obj]
            else:
                return convert_numpy_types(obj)
        
        # 转换统计数据
        stats = deep_convert(stats)
        
        # 确保结果目录存在
        # 使用self.data_dir作为基础目录，因为test_results_dir可能不存在
        stats_dir = self.data_dir
        os.makedirs(stats_dir, exist_ok=True)
        
        # 保存统计数据
        stats_file = os.path.join(stats_dir, 'data_statistics.json')
        with open(stats_file, 'w', encoding='utf-8') as f:
            json.dump(stats, f, ensure_ascii=False, indent=2)
        
        # 使用self.logger而不是logger
        self.logger.info(f'数据统计已保存至: {stats_file}')
        return stats
    
    def _visualize_statistics(self, stats):
        """可视化统计信息"""
        # 创建统计信息可视化
        fig, axes = plt.subplots(2, 2, figsize=(15, 10))
        fig.suptitle('数据统计可视化', fontsize=16)
        
        # 任务数据客户分布
        customer_counts = self.task_data['客户简称'].value_counts()
        axes[0, 0].pie(customer_counts, labels=customer_counts.index, autopct='%1.1f%%')
        axes[0, 0].set_title('任务数据客户分布')
        
        # 任务数据规格型号分布
        model_counts = self.task_data['规格型号'].value_counts().head(10)
        model_counts.plot(kind='bar', ax=axes[0, 1])
        axes[0, 1].set_title('任务数据规格型号分布（前10）')
        axes[0, 1].tick_params(axis='x', rotation=45)
        
        # 任务数据数量分布
        self.task_data['数量'].plot(kind='hist', bins=20, ax=axes[1, 0])
        axes[1, 0].set_title('任务数据数量分布')
        axes[1, 0].set_xlabel('数量')
        axes[1, 0].set_ylabel('频次')
        
        # 历史数据重量分布（如果有重量数据）
        if '重量' in self.history_data.columns and not self.history_data['重量'].empty:
            self.history_data['重量'].plot(kind='hist', bins=20, ax=axes[1, 1])
            axes[1, 1].set_title('历史数据重量分布')
            axes[1, 1].set_xlabel('重量')
            axes[1, 1].set_ylabel('频次')
        else:
            axes[1, 1].text(0.5, 0.5, '无重量数据', ha='center', va='center')
        
        plt.tight_layout(rect=[0, 0, 1, 0.96])
        plt.savefig(os.path.join(self.visual_dir, '数据统计可视化.png'), dpi=300)
        plt.close()
    
    def _determine_packaging_type(self, df, excel_file_path=None):
        """判定历史包装类型
        
        Args:
            df (pd.DataFrame): 历史数据框
            excel_file_path (str, optional): 原始Excel文件路径，如果提供则使用精确的合并单元格识别
            
        Returns:
            pd.DataFrame: 添加了包装类型列的数据框
        """
        # 创建数据副本
        df = df.copy()
        
        # 初始化包装类型列为独立包装
        df['包装类型'] = '独立包装'
        
        # 方法1: 如果提供了Excel文件路径，使用精确的合并单元格识别
        if excel_file_path and os.path.exists(excel_file_path) and excel_file_path.lower().endswith('.xlsx'):
            logger.info(f"使用精确合并单元格识别方法处理文件: {excel_file_path}")
            try:
                # 导入ExcelProcessor类
                from src.excel_handler.excel_processor import ExcelProcessor
                
                # 创建处理器实例
                excel_processor = ExcelProcessor()
                
                # 获取Excel结构信息
                structure = excel_processor.get_excel_structure(excel_file_path)
                if structure:
                    # 获取第一个工作表的合并单元格信息
                    first_sheet_name = list(structure['sheets'].keys())[0]
                    merged_cells = structure['sheets'][first_sheet_name].get('merged_cells', [])
                    
                    logger.info(f"找到 {len(merged_cells)} 个合并单元格")
                    
                    # 将DataFrame行索引映射到Excel行号（考虑header通常为第1行）
                    # 假设DataFrame的索引是从0开始，对应Excel的header_row+1行开始
                    header_row = 1  # 假设第一行为表头
                    
                    # 对于每个合并单元格，如果它跨越了多行，则标记这些行为组合包装
                    for merged_cell in merged_cells:
                        # 检查合并单元格是否至少跨越两行
                        if merged_cell['max_row'] > merged_cell['min_row']:
                            # 计算在DataFrame中的起始和结束索引
                            # 注意：需要考虑pandas读取Excel时的header设置
                            # 这里假设header=0（默认），所以Excel的第2行对应DataFrame的索引0
                            df_start_idx = merged_cell['min_row'] - (header_row + 1)
                            df_end_idx = merged_cell['max_row'] - (header_row + 1)
                            
                            # 确保索引在DataFrame范围内
                            if df_start_idx >= 0 and df_end_idx < len(df):
                                # 标记这些行为组合包装
                                df.loc[df_start_idx:df_end_idx, '包装类型'] = '组合包装'
                                logger.debug(f"标记行 {df_start_idx}-{df_end_idx} 为组合包装")
            except Exception as e:
                logger.error(f"精确合并单元格识别失败: {str(e)}，将回退到传统方法")
        
        # 方法2: 基于DataFrame内容识别组合包装（传统方法，作为回退）
        # 检查"包装箱方案"列是否存在
        if '包装类型' not in df.columns or (df['包装类型'] != '组合包装').all():
            # 如果方法1没有成功标记任何组合包装，或者没有提供Excel文件路径，则使用方法2
            logger.info("使用基于DataFrame内容的包装类型识别方法")
            
            if '包装箱方案' in df.columns:
                # 识别组合包装：基于Excel合并单元格的特性
                # 在读取Excel时，合并单元格的值通常只保留在第一个单元格，其他为NaN
                # 或者所有合并单元格都具有相同的值
                
                # 首先填充NaN值，以处理合并单元格中只有第一个有值的情况
                df['包装箱方案'] = df['包装箱方案'].ffill()
                
                # 查找连续的相同包装箱方案，这通常表示合并单元格
                # 遍历DataFrame来识别连续的相同值
                i = 0
                while i < len(df) - 1:
                    current_scheme = df.iloc[i]['包装箱方案']
                    # 跳过NaN值（未填充的情况）
                    if pd.isna(current_scheme):
                        i += 1
                        continue
                    
                    # 检查下一行是否有相同的方案
                    if i + 1 < len(df) and df.iloc[i + 1]['包装箱方案'] == current_scheme:
                        # 找到合并单元格区域的结束位置
                        j = i + 1
                        while j < len(df) and df.iloc[j]['包装箱方案'] == current_scheme:
                            j += 1
                        
                        # 标记整个合并单元格区域为组合包装
                        df.loc[i:j-1, '包装类型'] = '组合包装'
                        i = j  # 跳过已处理的行
                    else:
                        i += 1
        
        # 方法3: 基于其他特征识别组合包装
        # 检查是否有其他特征可以辅助判断包装类型
        if '客户订单号' in df.columns and '数量' in df.columns:
            # 查找具有相同订单号但不同产品规格的连续行
            i = 0
            while i < len(df) - 1:
                current_order = df.iloc[i]['客户订单号']
                # 跳过NaN值
                if pd.isna(current_order):
                    i += 1
                    continue
                
                # 查找相同订单号的连续行
                j = i + 1
                same_order_rows = []
                while j < len(df) and df.iloc[j]['客户订单号'] == current_order:
                    same_order_rows.append(j)
                    j += 1
                
                # 如果找到多个相同订单号的连续行，且当前行不是已标记的组合包装，则考虑为组合包装
                if same_order_rows and df.iloc[i]['包装类型'] != '组合包装':
                    # 检查是否有不同的产品规格或数量
                    has_different = False
                    for idx in same_order_rows:
                        # 可以根据实际情况调整这里的判断条件
                        if '规格型号' in df.columns and df.iloc[i]['规格型号'] != df.iloc[idx]['规格型号']:
                            has_different = True
                            break
                    
                    if has_different:
                        # 标记这些行为组合包装
                        df.loc[i:j-1, '包装类型'] = '组合包装'
                        logger.debug(f"基于订单号特征标记行 {i}-{j-1} 为组合包装")
                
                i = j  # 跳过已处理的行
        
        logger.info(f"包装类型识别完成，共识别 {len(df[df['包装类型'] == '组合包装'])} 条组合包装记录")
        return df
    
    def _visualize_packaging_types(self):
        """可视化包装类型分布"""
        if '包装类型' not in self.history_data.columns:
            return
        
        plt.figure(figsize=(12, 6))
        
        # 包装类型整体分布
        plt.subplot(1, 2, 1)
        packaging_counts = self.history_data['包装类型'].value_counts()
        plt.pie(packaging_counts, labels=packaging_counts.index, autopct='%1.1f%%')
        plt.title('历史数据包装类型分布')
        
        # 按客户分组的包装类型分布
        plt.subplot(1, 2, 2)
        customer_packaging = pd.crosstab(self.history_data['客户简称'], self.history_data['包装类型'])
        customer_packaging.plot(kind='bar', ax=plt.gca())
        plt.title('各客户包装类型分布')
        plt.xticks(rotation=45)
        plt.legend(title='包装类型')
        
        plt.tight_layout()
        plt.savefig(os.path.join(self.visual_dir, '包装类型分布.png'), dpi=300)
        plt.close()
    
    def _merge_task_data(self, df):
        """合并任务数据"""
        df = df.copy()
        
        # 按客户简称、规格型号、预完工日期合并
        group_cols = ['客户简称', '规格型号', '预完工日期']
        
        # 合并函数
        def merge_rows(group):
            # 合并订单号和行号
            order_ids = '/'.join(map(str, group['客户订单号'].unique()))
            line_numbers = '/'.join(map(str, group['行号'].unique()))
            
            # 计算总数量
            total_quantity = group['数量'].sum()
            
            # 创建新行
            new_row = group.iloc[0].copy()
            new_row['客户订单号'] = order_ids
            new_row['行号'] = line_numbers
            new_row['数量'] = total_quantity
            
            return new_row
        
        # 执行合并
        merged_df = df.groupby(group_cols, as_index=False).apply(merge_rows)
        
        # 添加新字段，但确保保留原始包装相关字段
        if '备注' not in merged_df.columns:
            merged_df['备注'] = ''
        if '产品归类' not in merged_df.columns:
            merged_df['产品归类'] = ''
        if '独立包装上限' not in merged_df.columns:
            merged_df['独立包装上限'] = ''
        
        return merged_df
    
    def _build_product_package_library(self):
        """建立产品包装方案库"""
        product_lib = {}
        
        # 获取所有任务数据中的规格型号
        task_models = self.task_data['规格型号'].unique()
        
        for model in task_models:
            # 查找该规格型号的所有历史包装方案
            model_history = self.history_data[self.history_data['规格型号'] == model].copy()
            
            if len(model_history) > 0:
                # 按包装类型分组
                package_by_type = {}
                
                # 处理独立包装
                independent_packages = model_history[model_history['包装类型'] == '独立包装']
                if len(independent_packages) > 0:
                    package_by_type['独立包装'] = independent_packages.to_dict('records')
                
                # 处理组合包装 - 将相同包装箱方案的记录作为整体处理
                if '组合包装' in model_history['包装类型'].values and '包装箱方案' in model_history.columns:
                    combined_packages = []
                    # 获取所有组合包装的唯一包装箱方案
                    combo_schemes = model_history[model_history['包装类型'] == '组合包装']['包装箱方案'].unique()
                    
                    for scheme in combo_schemes:
                        # 获取该方案下的所有记录
                        scheme_records = model_history[model_history['包装箱方案'] == scheme].copy()
                        
                        if len(scheme_records) > 0:
                            # 将整个方案作为一个组合包装条目添加
                            combined_package = {
                                '包装箱方案': scheme,
                                '包含产品': scheme_records[['规格型号', '数量']].to_dict('records'),
                                '预完工日期': scheme_records['预完工日期'].iloc[0] if '预完工日期' in scheme_records.columns else None,
                                '客户简称': scheme_records['客户简称'].iloc[0] if '客户简称' in scheme_records.columns else None
                            }
                            
                            # 添加其他可能存在的字段
                            for col in ['包装箱外口尺寸', '重量']:
                                if col in scheme_records.columns:
                                    # 获取第一个非空值
                                    non_null_values = scheme_records[col].dropna()
                                    if not non_null_values.empty:
                                        combined_package[col] = non_null_values.iloc[0]
                            
                            combined_packages.append(combined_package)
                    
                    if combined_packages:
                        package_by_type['组合包装'] = combined_packages
                
                # 判断独立包装上限
                has_upper_limit = False
                if len(independent_packages) > 0:
                    # 检查是否存在相同客户和日期的多个独立包装方案
                    grouped = independent_packages.groupby(['客户简称', '预完工日期']).size()
                    has_upper_limit = bool((grouped > 1).any())  # 确保转换为Python原生布尔类型
                
                # 数据清理：对于独立包装，按数量分组保留最新日期的方案
                if len(independent_packages) > 1:
                    # 按数量分组，每组保留最新的
                    independent_packages['预完工日期'] = pd.to_datetime(independent_packages['预完工日期'])
                    independent_packages = independent_packages.sort_values('预完工日期', ascending=False)
                    independent_packages = independent_packages.drop_duplicates(subset=['数量'], keep='first')
                
                product_lib[model] = {
                    'package_by_type': package_by_type,
                    'has_independent_limit': has_upper_limit,
                    'total_schemes': len(model_history)
                }
            else:
                # 无历史数据
                product_lib[model] = {
                    'package_by_type': {},
                    'has_independent_limit': False,
                    'total_schemes': 0
                }
        
        # 转换numpy类型为Python原生类型
        def convert_numpy_types(obj):
            if isinstance(obj, np.integer):
                return int(obj)
            elif isinstance(obj, np.floating):
                return float(obj)
            elif isinstance(obj, np.ndarray):
                return obj.tolist()
            elif isinstance(obj, np.bool_) or isinstance(obj, bool):
                return bool(obj)
            return obj
        
        # 深度转换字典中的numpy类型
        def deep_convert(obj):
            if isinstance(obj, dict):
                return {key: deep_convert(value) for key, value in obj.items()}
            elif isinstance(obj, list):
                return [deep_convert(item) for item in obj]
            else:
                return convert_numpy_types(obj)
        
        # 转换产品包装方案库
        product_lib = deep_convert(product_lib)
        
        return product_lib
    
    def _classify_products(self, product_lib):
        """产品归类"""
        df = self.task_data.copy()
        
        for idx, row in df.iterrows():
            model = row['规格型号']
            model_data = product_lib.get(model, {})
            package_types = model_data.get('package_by_type', {})
            
            # 判定产品归类
            if model_data.get('total_schemes', 0) == 0:
                df.at[idx, '产品归类'] = '无数据'
                df.at[idx, '备注'] = '设计项'
            elif '独立包装' in package_types and '组合包装' in package_types:
                df.at[idx, '产品归类'] = '混合包装'
            elif '独立包装' in package_types:
                df.at[idx, '产品归类'] = '仅独立包装'
            elif '组合包装' in package_types:
                df.at[idx, '产品归类'] = '仅组合包装'
            
            # 设置独立包装上限
            df.at[idx, '独立包装上限'] = '有' if model_data.get('has_independent_limit', False) else '无'
        
        return df
    
    def _visualize_product_classification(self):
        """可视化产品归类结果"""
        if '产品归类' not in self.task_data.columns:
            return
        
        plt.figure(figsize=(12, 6))
        
        # 产品归类分布
        plt.subplot(1, 2, 1)
        classification_counts = self.task_data['产品归类'].value_counts()
        plt.pie(classification_counts, labels=classification_counts.index, autopct='%1.1f%%')
        plt.title('产品归类分布')
        
        # 独立包装上限分布
        plt.subplot(1, 2, 2)
        if '独立包装上限' in self.task_data.columns:
            limit_counts = self.task_data['独立包装上限'].value_counts()
            plt.bar(limit_counts.index, limit_counts.values)
            plt.title('独立包装上限分布')
            plt.xlabel('独立包装上限')
            plt.ylabel('数量')
        else:
            plt.text(0.5, 0.5, '无独立包装上限数据', ha='center', va='center')
        
        plt.tight_layout()
        plt.savefig(os.path.join(self.visual_dir, '产品归类结果.png'), dpi=300)
        plt.close()
    
    def _classify_tasks(self, product_package_lib):
        """任务分类处理"""
        df = self.task_data.copy()
        new_rows = []  # 用于存储拆分后的新行
        rows_to_drop = []  # 用于存储需要删除的原始行
        
        # 获取当前最大行号
        current_max_line = 0
        if not df.empty and '行号' in df.columns:
            # 尝试将行号转换为数字进行比较
            try:
                # 处理可能的字符串行号
                line_numbers = []
                for line in df['行号']:
                    try:
                        # 尝试提取数字部分
                        num_str = ''.join(filter(str.isdigit, str(line)))
                        if num_str:
                            line_numbers.append(int(num_str))
                    except (ValueError, TypeError):
                        continue
                if line_numbers:
                    current_max_line = max(line_numbers)
            except Exception:
                current_max_line = 0
        
        for idx, row in df.iterrows():
            # 如果已经有备注，跳过
            if row['备注'] == '设计项':
                continue
            
            # 步骤1：判断产品归类
            if row['产品归类'] == '仅组合包装':
                df.at[idx, '备注'] = '参考项（仅组合包装）'
            elif row['产品归类'] in ['仅独立包装', '混合包装']:
                # 步骤2：独立包装数量精确匹配
                # 查找产品包装方案库中的独立包装方案数量值
                model = row['规格型号']
                model_data = product_package_lib.get(model, {})
                package_types = model_data.get('package_by_type', {})
                
                # 获取独立包装方案中的数量列表
                independent_packages = package_types.get('独立包装', [])
                quantity_values = []
                for pkg in independent_packages:
                    if isinstance(pkg, dict) and '数量' in pkg and pkg['数量'] > 0:
                        quantity_values.append(pkg['数量'])
                
                # 精确匹配检查 - 如果任务数量在独立包装数量中，直接复用
                if row['数量'] in quantity_values:
                    df.at[idx, '备注'] = '复用项'
                else:
                    # 步骤3：数量关系分析
                    # 计算最小包装数量
                    min_quantity = min(quantity_values) if quantity_values else float('inf')
                    
                    if row['数量'] < min_quantity:
                        df.at[idx, '备注'] = f'参考项（{"仅独立包装" if row["产品归类"] == "仅独立包装" else "混合包装"}）'
                    else:
                        # 步骤4：检查独立包装上限 - 修改为不依赖独立包装上限列
                        if quantity_values:  # 只要有可用的独立包装数量，就尝试拆分
                            # 步骤5：迭代拆分及任务数据处理 - 实现真正的拆分逻辑
                            total_quantity = row['数量']
                            
                            # 优先选择与目标数量最接近的包装方案
                            # 如果没有精确匹配，检查是否有接近的数量（优先选择较大的）
                            closest_match = None
                            min_diff = float('inf')
                            
                            for qty in sorted(quantity_values, reverse=True):
                                # 如果数量小于等于目标数量，计算差值
                                if qty <= total_quantity:
                                    diff = total_quantity - qty
                                    # 优先选择较大的数量，如果差值相同则选更大的
                                    if diff < min_diff or (diff == min_diff and qty > closest_match):
                                        min_diff = diff
                                        closest_match = qty
                            
                            # 如果找到合适的最接近匹配，直接使用它进行拆分
                            if closest_match:
                                # 计算可以拆分的数量
                                count = total_quantity // closest_match
                                remaining = total_quantity % closest_match
                                
                                # 如果剩余数量小于最小包装数量，则进行拆分
                                if remaining < min_quantity:
                                    split_result = [closest_match] * count
                                else:
                                    # 否则使用原来的优化拆分算法
                                    split_result = self._optimize_split(total_quantity, quantity_values)
                            else:
                                # 没有找到合适的最接近匹配，使用原来的优化拆分算法
                                split_result = self._optimize_split(total_quantity, quantity_values)
                            
                            if split_result:
                                # 计算拆分后的总量
                                split_total = sum(split_result)
                                remaining_quantity = total_quantity - split_total
                                
                                # 记录需要删除的原始行
                                rows_to_drop.append(idx)
                                
                                # 添加拆分后的行，为每行添加递增的行号和递增序号
                            split_index = 1  # 拆分序号，从1开始递增
                            
                            for qty in split_result:
                                # 找到对应的包装方案
                                package_info = None
                                for pkg in independent_packages:
                                    if isinstance(pkg, dict) and pkg.get('数量') == qty:
                                        package_info = pkg
                                        break
                                
                                # 创建新行
                                new_row = row.copy()
                                new_row['数量'] = qty
                                new_row['备注'] = '复用项'
                                
                                # 为拆分后的行添加递增的行号和递增序号，格式为"行号-序号"
                                current_max_line += 1
                                new_row['行号'] = f"{current_max_line}-{split_index}"
                                split_index += 1  # 拆分序号递增
                                
                                # 添加包装方案信息（如果有）
                                if package_info:
                                    if '包装箱方案' in package_info:
                                        new_row['包装箱方案'] = package_info['包装箱方案']
                                    if '包装箱外口尺寸' in package_info:
                                        new_row['包装箱外口尺寸'] = package_info['包装箱外口尺寸']
                                    if '重量' in package_info:
                                        new_row['重量'] = package_info['重量']
                                
                                new_rows.append(new_row)
                            
                            # 处理剩余数量（如果有）
                            if remaining_quantity > 0 and remaining_quantity < min_quantity:
                                remaining_row = row.copy()
                                remaining_row['数量'] = remaining_quantity
                                remaining_row['备注'] = f'参考项（{"仅独立包装" if row["产品归类"] == "仅独立包装" else "混合包装"}）'
                                
                                # 为剩余行添加递增的行号和递增序号
                                current_max_line += 1
                                remaining_row['行号'] = f"{current_max_line}-{split_index}"  # 使用下一个递增序号
                                
                                new_rows.append(remaining_row)
                            else:
                                df.at[idx, '备注'] = f'参考项（{"仅独立包装" if row["产品归类"] == "仅独立包装" else "混合包装"}）'
                        else:
                            df.at[idx, '备注'] = f'参考项（{"仅独立包装" if row["产品归类"] == "仅独立包装" else "混合包装"}）'
        
        # 删除需要拆分的原始行
        df = df.drop(rows_to_drop)
        
        # 添加拆分后的新行
        if new_rows:
            new_df = pd.DataFrame(new_rows)
            df = pd.concat([df, new_df], ignore_index=True)
        
        return df
    
    def _optimize_split(self, total_quantity, available_quantities):
        """优化拆分算法
        
        Args:
            total_quantity: 需要拆分的总数量
            available_quantities: 可用的包装数量列表
            
        Returns:
            list: 拆分结果列表，当可以完全拆分或剩余数量小于最小包装数量时返回有效结果
        """
        # 去重并按降序排序可用数量
        sorted_quantities = sorted(set(available_quantities), reverse=True)
        if not sorted_quantities:
            return None
            
        min_package = min(sorted_quantities)
        best_result = None
        best_score = float('-inf')
        
        # 首先尝试精确拆分
        exact_result = self._find_exact_split(total_quantity, sorted_quantities)
        if exact_result:
            return sorted(exact_result, reverse=True)
        
        # 如果无法精确拆分，尝试找到剩余数量小于最小值的最佳方案
        # 使用回溯法尝试所有可能的组合
        def backtrack(remaining, current_split, start_index):
            nonlocal best_result, best_score
            
            # 检查当前拆分是否符合要求（剩余数量小于最小值）
            if remaining >= 0 and remaining < min_package:
                # 计算得分：优先考虑剩余数量少，其次考虑使用的包装种类少
                score = -remaining - len(set(current_split)) * 0.1
                if score > best_score:
                    best_score = score
                    best_result = current_split.copy()
                return
            
            # 尝试每种可能的包装数量，支持重复使用（start_index不增加，允许重复使用当前包装数量）
            for i in range(start_index, len(sorted_quantities)):
                qty = sorted_quantities[i]
                if qty > remaining:
                    continue
                    
                max_count = remaining // qty
                for count in range(max_count, 0, -1):
                    # 添加当前包装数量
                    new_split = current_split + [qty] * count
                    # 继续尝试当前或下一个包装数量（支持重复使用）
                    backtrack(remaining - qty * count, new_split, i)
        
        # 开始回溯搜索
        backtrack(total_quantity, [], 0)
        
        # 对找到的最佳结果进行排序
        if best_result:
            return sorted(best_result, reverse=True)
        
        return None
    
    def _find_exact_split(self, target, options, path=None):
        """递归查找精确拆分方案"""
        if path is None:
            path = []
        
        # 基本情况：已找到精确拆分
        if target == 0:
            return path
        
        # 尝试每种可能的包装数量，支持重复使用同一个包装数量
        for option in options:
            if option > target:
                continue
                
            # 计算可以使用多少个当前包装
            max_count = target // option
            for count in range(max_count, 0, -1):
                new_target = target - (option * count)
                new_path = path + [option] * count
                
                # 移除对选项的过滤，允许重复使用同一个包装数量
                result = self._find_exact_split(new_target, options, new_path)
                if result:
                    return result
        
        return None
    
    def _visualize_task_classification(self):
        """可视化任务分类结果"""
        if '备注' not in self.task_data.columns:
            return
        
        plt.figure(figsize=(10, 6))
        
        # 任务分类分布
        task_counts = self.task_data['备注'].value_counts()
        task_counts.plot(kind='bar')
        plt.title('任务分类结果分布')
        plt.xlabel('任务类型')
        plt.ylabel('数量')
        plt.xticks(rotation=45)
        
        # 在柱状图上添加数值标签
        for i, v in enumerate(task_counts):
            plt.text(i, v + 0.5, str(v), ha='center')
        
        plt.tight_layout()
        plt.savefig(os.path.join(self.visual_dir, '任务分类结果.png'), dpi=300)
        plt.close()
    
    def _match_data(self):
        """数据匹配"""
        df = self.task_data.copy()
        
        # 从产品包装方案库中查找并填充对应的包装方案
        for idx, row in df.iterrows():
            model = row['规格型号']
            product_data = self._get_product_from_library(model)
            quantity = row.get('数量', 0)
            
            # 如果找到产品对应的包装方案
            if product_data and 'package_by_type' in product_data:
                package_types = product_data['package_by_type']
                
                # 根据任务类型和产品归类选择合适的包装类型
                note = row.get('备注', '')
                classification = row.get('产品归类', '')
                
                # 对于复用项，必须根据数量字段进行精确匹配
                if note == '复用项':
                    # 优先在独立包装中查找数量匹配的方案
                    if '独立包装' in package_types and package_types['独立包装']:
                        # 查找数量完全匹配的包装方案
                        matched_package = None
                        for package in package_types['独立包装']:
                            if isinstance(package, dict) and package.get('数量') == quantity:
                                matched_package = package
                                break
                        
                        # 如果找到匹配的包装方案，填充信息
                        if matched_package:
                            if '包装箱方案' in matched_package:
                                df.at[idx, '包装箱方案'] = matched_package['包装箱方案']
                            if '包装箱外口尺寸' in matched_package:
                                df.at[idx, '包装箱外口尺寸'] = matched_package['包装箱外口尺寸']
                            if '重量' in matched_package:
                                df.at[idx, '重量'] = matched_package['重量']
                else:
                    # 对于非复用项，根据产品归类选择包装类型
                    if classification == '混合包装':
                        # 对于混合包装，优先选择独立包装方案
                        if '独立包装' in package_types and package_types['独立包装']:
                            # 选择第一个独立包装方案
                            package_info = package_types['独立包装'][0]
                            if '包装箱方案' in package_info:
                                df.at[idx, '包装箱方案'] = package_info['包装箱方案']
                            if '包装箱外口尺寸' in package_info:
                                df.at[idx, '包装箱外口尺寸'] = package_info['包装箱外口尺寸']
                            if '重量' in package_info:
                                df.at[idx, '重量'] = package_info['重量']
                    elif classification == '仅独立包装' and '独立包装' in package_types and package_types['独立包装']:
                        package_info = package_types['独立包装'][0]
                        if '包装箱方案' in package_info:
                            df.at[idx, '包装箱方案'] = package_info['包装箱方案']
                        if '包装箱外口尺寸' in package_info:
                            df.at[idx, '包装箱外口尺寸'] = package_info['包装箱外口尺寸']
                        if '重量' in package_info:
                            df.at[idx, '重量'] = package_info['重量']
                    elif classification == '仅组合包装' and '组合包装' in package_types and package_types['组合包装']:
                        package_info = package_types['组合包装'][0]
                        if '包装箱方案' in package_info:
                            df.at[idx, '包装箱方案'] = package_info['包装箱方案']
                        if '包装箱外口尺寸' in package_info:
                            df.at[idx, '包装箱外口尺寸'] = package_info['包装箱外口尺寸']
                        if '重量' in package_info:
                            df.at[idx, '重量'] = package_info['重量']
        
        return df
    
    def _get_product_from_library(self, model):
        """从产品包装方案库中获取产品信息"""
        # 加载产品包装方案库
        lib_path = os.path.join(self.data_dir, "2-3-产品包装方案库.json")
        try:
            with open(lib_path, 'r', encoding='utf-8') as f:
                product_lib = json.load(f)
                return product_lib.get(model, None)
        except:
            return None
    
    def _visualize_matching_results(self):
        """可视化匹配结果"""
        # 匹配成功率分析
        matched_count = self.task_data['包装箱方案'].notna().sum()
        total_count = len(self.task_data)
        match_rate = (matched_count / total_count) * 100 if total_count > 0 else 0
        
        plt.figure(figsize=(12, 6))
        
        # 匹配成功vs失败
        plt.subplot(1, 2, 1)
        plt.pie([matched_count, total_count - matched_count], 
                labels=['匹配成功', '匹配失败'], 
                autopct='%1.1f%%')
        plt.title(f'匹配成功率: {match_rate:.1f}%')
        
        # 各类型任务的匹配情况
        plt.subplot(1, 2, 2)
        if '备注' in self.task_data.columns:
            match_by_type = pd.crosstab(
                self.task_data['备注'], 
                self.task_data['包装箱方案'].notna(),
                rownames=['任务类型'],
                colnames=['是否匹配']
            )
            match_by_type.plot(kind='bar', ax=plt.gca(), stacked=True)
            plt.title('各类型任务匹配情况')
            plt.xticks(rotation=45)
            plt.legend(['未匹配', '已匹配'])
        else:
            plt.text(0.5, 0.5, '无任务类型数据', ha='center', va='center')
        
        plt.tight_layout()
        plt.savefig(os.path.join(self.visual_dir, '匹配结果分析.png'), dpi=300)
        plt.close()
    
    def _validate_and_correct_data(self):
        """数据校验与修正"""
        df = self.task_data.copy()
        
        # 检查必填字段
        required_fields = ['客户订单号', '客户简称', '规格型号', '数量']
        for field in required_fields:
            if field in df.columns:
                # 填充缺失值
                df[field] = df[field].fillna('未知')
        
        # 检查数量字段
        if '数量' in df.columns:
            # 确保数量为正数
            df['数量'] = df['数量'].apply(lambda x: max(1, x) if pd.notna(x) and x > 0 else 1)
        
        return df
    
    def _format_output_data(self):
        """格式化输出数据"""
        # 按照项目要求的输出格式进行整理
        output_columns = ['客户订单号', '行号', '客户简称', '规格型号', '数量', '预完工日期', 
                         '包装箱外口尺寸', '重量', '包装箱方案', '备注']
        
        # 确保所有输出列存在
        output_df = pd.DataFrame()
        for col in output_columns:
            if col in self.task_data.columns:
                output_df[col] = self.task_data[col]
            else:
                output_df[col] = ''
        
        # 根据项目大纲要求进行排序
        # 1. 先按照"备注"字段排序，顺序依次为：
        #    "复用项"、"设计项"、"参考项（仅独立包装）"、"参考项（仅组合包装）"、"参考项（混合包装）"
        # 2. 再按照"客户简称"、"预完工日期"、"规格型号"进行升序排序
        
        # 定义排序顺序
        sort_order = {
            '复用项': 0,
            '设计项': 1,
            '参考项（仅独立包装）': 2,
            '参考项（仅组合包装）': 3,
            '参考项（混合包装）': 4
        }
        
        # 将备注字段映射为排序值
        output_df['_sort_key'] = output_df['备注'].map(lambda x: sort_order.get(x, 999))
        
        # 执行排序
        output_df = output_df.sort_values(
            by=['_sort_key', '客户简称', '预完工日期', '规格型号'],
            ascending=[True, True, True, True]
        )
        
        # 删除临时排序键
        output_df = output_df.drop('_sort_key', axis=1)
        
        return output_df
    
    def _generate_final_report(self):
        """生成最终报告"""
        # 创建最终结果的汇总报告
        plt.figure(figsize=(15, 12))
        
        # 1. 任务处理结果概览
        plt.subplot(2, 2, 1)
        task_status_counts = self.task_data['备注'].value_counts()
        task_status_counts.plot(kind='pie', autopct='%1.1f%%')
        plt.title('任务处理结果概览')
        
        # 2. 包装方案使用情况
        plt.subplot(2, 2, 2)
        if '包装箱方案' in self.task_data.columns:
            package_counts = self.task_data['包装箱方案'].value_counts()
            package_counts.plot(kind='bar')
            plt.title('包装方案使用情况')
            plt.xticks(rotation=45)
        else:
            plt.text(0.5, 0.5, '无包装方案数据', ha='center', va='center')
        
        # 3. 重量分布（如果有数据）
        plt.subplot(2, 2, 3)
        if '重量' in self.task_data.columns and not self.task_data['重量'].empty:
            weights = pd.to_numeric(self.task_data['重量'], errors='coerce').dropna()
            weights.plot(kind='hist', bins=20)
            plt.title('包装重量分布')
            plt.xlabel('重量')
        else:
            plt.text(0.5, 0.5, '无重量数据', ha='center', va='center')
        
        # 4. 客户任务分布
        plt.subplot(2, 2, 4)
        customer_task_counts = self.task_data.groupby('客户简称').size()
        customer_task_counts.plot(kind='bar')
        plt.title('客户任务分布')
        plt.xticks(rotation=45)
        
        plt.tight_layout(rect=[0, 0, 1, 0.96])
        plt.suptitle('包装方案匹配工具 - 处理结果报告', fontsize=16)
        plt.savefig(os.path.join(self.visual_dir, '最终结果报告.png'), dpi=300)
        plt.close()
        
        # 生成HTML报告
        self._generate_html_report()
    
    def _generate_html_report(self):
        """生成HTML格式的报告"""
        report_path = os.path.join(self.test_dir, "测试报告.html")
        
        # 收集报告数据
        stats = {}
        stats_file = os.path.join(self.data_dir, "data_statistics.json")
        if os.path.exists(stats_file):
            try:
                with open(stats_file, 'r', encoding='utf-8') as f:
                    stats = json.load(f)
            except json.JSONDecodeError as e:
                self.logger.warning(f"无法解析统计数据文件: {str(e)}，将使用默认统计数据")
        
        # 如果没有统计数据或解析失败，生成默认数据
        if not stats:
            stats = {
                'task_data': {
                    'total_rows': len(self.task_data) if self.task_data is not None else 0,
                    'unique_customers': self.task_data['客户简称'].nunique() if self.task_data is not None and '客户简称' in self.task_data.columns else 0,
                    'unique_models': self.task_data['规格型号'].nunique() if self.task_data is not None and '规格型号' in self.task_data.columns else 0
                },
                'history_data': {
                    'total_rows': len(self.history_data) if self.history_data is not None else 0
                }
            }
        
        # HTML内容
        html_content = f"""
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>瀚越包装方案匹配工具 - 测试报告</title>
            <style>
                body {{
                    font-family: 'Microsoft YaHei', Arial, sans-serif;
                    line-height: 1.6;
                    color: #333;
                    max-width: 1200px;
                    margin: 0 auto;
                    padding: 20px;
                    background-color: #f5f5f5;
                }}
                h1, h2, h3 {{
                    color: #2c3e50;
                }}
                .report-header {{
                    background-color: #3498db;
                    color: white;
                    padding: 20px;
                    border-radius: 8px;
                    margin-bottom: 30px;
                }}
                .section {{
                    background-color: white;
                    padding: 25px;
                    margin-bottom: 20px;
                    border-radius: 8px;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                }}
                .stats-grid {{
                    display: grid;
                    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
                    gap: 20px;
                    margin: 20px 0;
                }}
                .stat-card {{
                    background-color: #ecf0f1;
                    padding: 15px;
                    border-radius: 6px;
                    text-align: center;
                }}
                .stat-value {{
                    font-size: 24px;
                    font-weight: bold;
                    color: #3498db;
                }}
                .image-container {{
                    margin: 20px 0;
                    text-align: center;
                }}
                .image-container img {{
                    max-width: 100%;
                    height: auto;
                    border-radius: 8px;
                    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
                }}
                .image-caption {{
                    margin-top: 10px;
                    font-style: italic;
                    color: #666;
                }}
                table {{
                    width: 100%;
                    border-collapse: collapse;
                    margin: 20px 0;
                }}
                th, td {{
                    padding: 12px;
                    text-align: left;
                    border-bottom: 1px solid #ddd;
                }}
                th {{
                    background-color: #f8f9fa;
                    font-weight: bold;
                }}
                tr:hover {{
                    background-color: #f5f5f5;
                }}
            </style>
        </head>
        <body>
            <div class="report-header">
                <h1>瀚越包装方案匹配工具 - 测试报告</h1>
                <p>生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
            </div>
            
            <div class="section">
                <h2>1. 数据概览</h2>
                <div class="stats-grid">
                    <div class="stat-card">
                        <div class="stat-value">{stats.get('task_data', {}).get('total_rows', 0)}</div>
                        <div>任务数据总行数</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value">{stats.get('task_data', {}).get('unique_customers', 0)}</div>
                        <div>客户数量</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value">{stats.get('task_data', {}).get('unique_models', 0)}</div>
                        <div>产品规格数量</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-value">{stats.get('history_data', {}).get('total_rows', 0)}</div>
                        <div>历史数据总行数</div>
                    </div>
                </div>
            </div>
            
            <div class="section">
                <h2>2. 数据处理流程</h2>
                
                <h3>2.1 前处理阶段</h3>
                <p>完成了文件选取、数据导入、数据格式化和数据统计等操作。</p>
                <div class="image-container">
                    <img src="visualizations/数据统计可视化.png" alt="数据统计可视化">
                    <div class="image-caption">数据统计可视化</div>
                </div>
                
                <h3>2.2 数据预处理阶段</h3>
                <p>完成了历史包装类型判定、数据合并、建立产品包装方案库和产品归类等操作。</p>
                <div class="image-container">
                    <img src="visualizations/包装类型分布.png" alt="包装类型分布">
                    <div class="image-caption">包装类型分布</div>
                </div>
                <div class="image-container">
                    <img src="visualizations/产品归类结果.png" alt="产品归类结果">
                    <div class="image-caption">产品归类结果</div>
                </div>
                
                <h3>2.3 任务分类与数据匹配阶段</h3>
                <p>完成了任务分类处理、数据匹配和数据校验与修正等操作。</p>
                <div class="image-container">
                    <img src="visualizations/任务分类结果.png" alt="任务分类结果">
                    <div class="image-caption">任务分类结果</div>
                </div>
                <div class="image-container">
                    <img src="visualizations/匹配结果分析.png" alt="匹配结果分析">
                    <div class="image-caption">匹配结果分析</div>
                </div>
                
                <h3>2.4 数据后处理阶段</h3>
                <p>完成了数据输出与格式化操作，并生成最终报告。</p>
            </div>
            
            <div class="section">
                <h2>3. 处理结果分析</h2>
                <div class="image-container">
                    <img src="visualizations/最终结果报告.png" alt="最终结果报告">
                    <div class="image-caption">最终结果报告</div>
                </div>
            </div>
            
            <div class="section">
                <h2>4. 总结与建议</h2>
                <p>测试完成，所有数据处理流程正常运行。</p>
                <p>建议：</p>
                <ul>
                    <li>使用真实数据进行进一步测试，验证算法的准确性</li>
                    <li>优化数据匹配算法，提高匹配成功率</li>
                    <li>根据实际业务需求调整产品归类和任务分类的规则</li>
                </ul>
            </div>
        </body>
        </html>
        """
        
        # 保存HTML报告
        with open(report_path, 'w', encoding='utf-8') as f:
            f.write(html_content)
        
        logger.info(f"HTML报告已生成: {report_path}")
    
    def _save_dataframe(self, df, name, prefix=""):
        """保存数据框到文件"""
        # 检查数据框是否为None
        if df is None:
            logger.warning(f"无法保存{name}，数据框为空")
            return
        
        # 添加步骤前缀
        full_name = f"{prefix}{name}" if prefix else name
        
        # 保存为Excel
        excel_path = os.path.join(self.data_dir, f"{full_name}.xlsx")
        success = self.excel_processor.write_excel(df, excel_path)
        if success:
            logger.info(f"{full_name}已保存至: {excel_path}")
        
        # 保存为CSV（方便查看）
        csv_path = os.path.join(self.data_dir, f"{full_name}.csv")
        df.to_csv(csv_path, encoding='utf-8-sig', index=False)
        logger.info(f"{full_name}已保存至: {csv_path}")

if __name__ == "__main__":
    import logging
    
    # 设置日志级别
    logging.basicConfig(level=logging.INFO, 
                        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    # 创建测试器并运行测试
    tester = ProcessTester()
    
    try:
        logger.info("开始运行测试流程")
        start_time = time.time()
        
        # 运行测试（这里使用模拟数据）
        tester.run_test()
        
        end_time = time.time()
        logger.info(f"测试流程运行完成，耗时: {end_time - start_time:.2f} 秒")
        logger.info(f"测试结果保存在: {tester.test_dir}")
        
    except Exception as e:
        logger.error(f"测试运行失败: {str(e)}")
        raise